import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:岁亮亮
 * Date:2024-10-24
 * TIME:11:48
 */
public class TCPEchoServer {

    //定义一个操作网卡设备的api对象
    private ServerSocket serverSocket = null;

    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        //serverSocket是主要用来接收客户端发来的连接请求,用于监听哪个客户端要与服务器进行通信的
        //有客户端发来连接的请求时serverSocket建立连接后不与客户端进行通信而是交给专门与客户端进行通信的socket对象
        //serverSocket对象与一个客户端建立连接之后,交给socket对象与客户端进行通信返回响应后,又继续进行监听还有那些客户端要连接通信的
        while (true){
            Socket clientSocket = serverSocket.accept();
            proceessSocket(clientSocket);
        }
    }
    //
    private void proceessSocket(Socket clientSocket) throws IOException {
        //TCP 在accept()中已经和客户端建立连接了 socket 中会保存对端的信息
        System.out.printf("[%s : %d]  客户端上线 " ,clientSocket.getInetAddress(),clientSocket.getPort());
        //每建立一次连接后,都要有反复很多次的请求和要返回的回应 processSocket方法就是处理客户端发来的请求和返回响应
        //接收请求
        //读取请求并计算响应
        //构造要返回的响应
        //发送响应
        try ( InputStream inputStream = clientSocket.getInputStream();
              OutputStream outputStream = clientSocket.getOutputStream()){
            //TCP是面向字节流的 传输的单位就是字节,并且这里的字节流与操作文件的字节流是一致的,可以用Input和Output类
            //不用像UDP 那样又封装一个类来作为传输的单位

            //从文件中读取数据

            while (true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d]客户端下线 ",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //这里有一个隐藏的约定 .next()方法读取字符串的时候必去要有空白符 才会读取结束,要求客户端传来的数据以空白符结尾
                //相当于在此约定 每个请求就是以空白符作为 分隔
                //如果发的数据报有多个空白符 就分多次发送
                String request = scanner.next();
                //根据请求计算响应
                String reponse = process(request);
                //写入响应 并返回.
                //这里也可以在outputStream中写入 但写入的响应 会没有空白符结尾 ,不方便加\n
                //outputStream.write(reponse.getBytes(),0,reponse.getBytes().length);
                //可以给outputStream套上一层 就更方便了
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(reponse);
                printWriter.flush();
                System.out.printf("[%s : %d] res : %s ,resp :  %s\n" , clientSocket.getInetAddress(),clientSocket.getPort(),request,reponse);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            clientSocket.close();
            //当客户端不在要求通信时把服务器专门与客户端通信的clientSocket对象关闭
            //因为当要求建立的客户端越多即serverSocket建立的连接越多 消耗的clientSocket对象如果不加以释放 可能导致文件描述符表耗尽了
            //关闭了clientSocket对象 之后 他所创建的流对象也关闭了 但流对象所创建的文件主题并没有释放 ,
            // 不过问题也不大,这两个流对象又不持有文件描述符表,只是持有一些内存结构,它可以被GC释放
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TCPEchoServer t = new TCPEchoServer(9090);
        t.start();
    }
}
